﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO.Ports;
using System.IO;

namespace CANLib
{

    /// <summary>
    /// This class privides all methodods needed for communication with the hardware
    /// </summary>
    public class HardwareCommunicator: IDisposable
    {
        #region ctor

        public HardwareCommunicator(String portName)
        {
            this.mPort = new SerialPort(portName);
            this.mLogFile = new StreamWriter(File.Open("Log.txt", FileMode.Append));
        }

        public HardwareCommunicator(SerialPort port)
        {
            this.mPort = port;
            this.mLogFile = new StreamWriter(File.Open("Log.txt", FileMode.Append));
        }

        ~HardwareCommunicator()
        {
            this.Dispose();
        }

        #endregion

        #region Members

        private Thread mTestThread = null;

        private SerialPort mPort = null;
        private HardwareInfo mDeviceInfo;
        private bool mIsConnected = false;

        private StreamWriter mLogFile = null;

        private string mError = "";

        #endregion

        #region Properties

        /// <summary>
        /// Gets if a hardware device is connected
        /// </summary>
        public bool IsConnected
        {
            get
            {
                return TestConnection();
            }
        }

        //----------------------------------------------------------------------------------------

        /// <summary>
        /// Gets informations about the device
        /// </summary>
        public HardwareInfo DeviceInfo
        {
            get
            {
                //Check if connection is established
                if (this.IsConnected)
                {
                    //Check if device info has already been built
                    if (mDeviceInfo.IsEmpty)
                    {
                        this.BuildDeviceInfo();
                    }

                    return mDeviceInfo;
                }
                else
                {
                    throw new Exception("Hardware could not be found!");
                }
            }
        }

        //----------------------------------------------------------------------------------------

        /// <summary>
        /// Gets the error string if error has occoured otherwise string is empty
        /// </summary>
        public string Error
        {
            get
            {
                string ret = this.mError;
                this.mError = "";   //Reset error after reading
                return ret;
            }
        }

        //----------------------------------------------------------------------------------------

        /// <summary>
        /// Gets if the communicator has had an error since this proterty has been checked last time
        /// </summary>
        public bool HasError
        {
            get
            {
                return this.mError.Equals("");
            }
        }

        //----------------------------------------------------------------------------------------

        #endregion

        #region Methods

        /// <summary>
        /// Tests if a hardware device is connected
        /// </summary>
        /// <returns>True if connection is established</returns>
        private bool TestConnection()
        {
            //Check if test thread should be used
            if (this.mTestThread == null)
            {
                //Check if port is available
                if (this.mPort != null)
                {
                    try
                    {
                        //Check if port is open (assumption is not open)
                        if (!this.mPort.IsOpen)
                        {
                            this.mPort.Open();
                        }

                        //Test connection
                        this.mIsConnected = this.PingHardware();
                    }
                    catch (Exception e)
                    {
                        this.mError = e.Message;    //Set error message
                        this.mIsConnected = false;
                    }
                }//if(mPort)
            }//if(mTestThread)
            else
            {
                //Test thread is always connected
                this.mIsConnected = true;
            }

            return this.mIsConnected;
        }

        //----------------------------------------------------------------------------------------

        /// <summary>
        /// Retreives the information from the hardware device and saves it in mDeviceInfo
        /// </summary>
        /// <returns>True if informations have successfully been retreived</returns>
        private bool BuildDeviceInfo()
        {
            bool success = false;

            return success;
        }

        //----------------------------------------------------------------------------------------

        /// <summary>
        /// Starts capturing from the hardware
        /// </summary>
        /// <returns>True if starting capture was successfull otherwise false</returns>
        public bool Start()
        {
            bool success = true;

            if (mPort.PortName == "Test" && mTestThread == null)
            {
                mTestThread = new Thread(new ThreadStart(ThreadProg));
                mTestThread.Start();

                success = true;
            }
            else
            {
                //Test connection and ask if hardware connected is the right hardware
                if (this.TestConnection())
                {
                    //Setup serial device for communication
                    this.mPort.DiscardNull = false;
                    this.mPort.ReceivedBytesThreshold = 4;

                    //Send start signal to hardware
                    this.mPort.Write(new byte[] { 0x01 }, 0, 1);

                    if (!this.ReadAck())
                    {
                        //If timout occured ReadAck has set an error
                        if (!this.HasError)
                        {
                            //There was no timeout -> ack byte was wrong
                            this.mError = "Received ACK not valid";
                        }
                        success = false;
                    }

                    //Add event after starting
                    this.mPort.DataReceived += new SerialDataReceivedEventHandler(this.SerialReceivedProg);
                }
                else
                {
                    //Failure when testing connection
                    success = false;
                }
            }

            return success;
        }

        //----------------------------------------------------------------------------------------

        /// <summary>
        /// Stops capturing
        /// </summary>
        public void Stop()
        {
            if (this.mTestThread != null && this.mTestThread.IsAlive)
            {
                mTestThread.Abort();
            }
            else
            {
                if (this.mPort.IsOpen)
                {
                    //Send stop message to hardware
                    this.mPort.Write(new byte[] { 0x02 }, 0, 1);

                    this.ReadAck();
                }
            }
        }

        //----------------------------------------------------------------------------------------

        /// <summary>
        /// Tests if the connected hardware is rifht and running
        /// </summary>
        /// <returns>True if ping was successfull, otherwise false</returns>
        public bool PingHardware()
        {
            bool ping = false;

            if (this.mPort.IsOpen)
            {
                this.mPort.Write(new byte[] {0x03}, 0, 1);

                //Read acknowledge message from hardware
                if (!this.ReadAck())
                {
                    //Timeout

                    this.mError = "Timeout";    //Set timeout error
                    ping  = false;
                }
                else
                {
                    ping = true;
                }
            }

            return ping;
        }

        //----------------------------------------------------------------------------------------

        /// <summary>
        /// Reads tries to read one byte from serial port (timout = 500msec)
        /// </summary>
        /// <returns>True if ack has been read, false if byte was no ack or timeout occured (when timeout occured mError is filled)</returns>
        private bool ReadAck()
        {
            bool ack = false;

            if (this.mPort != null && this.mPort.IsOpen)
            {
                int i = 0;

                //Try to read ack byte from the serial device (max 500msec)
                while (this.mPort.BytesToRead <= 0 && i < 10)
                {
                    Thread.Sleep(50);   //Wait until next try
                    i++;
                }

                //Read acknowledge message from hardware
                if (this.mPort.BytesToRead <= 0)
                {
                    //Timeout

                    this.mError = "Timeout";    //Set timeout error
                    ack = false;
                }
                else 
                {
                    byte ackByte = (byte)this.mPort.ReadByte();
                    if (ackByte == 0x00)
                        ack = true;
                }
            }

            return ack;
        }

        //----------------------------------------------------------------------------------------

        /// <summary>
        /// Is called when serial port receives data
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void SerialReceivedProg(object sender, SerialDataReceivedEventArgs args)
        {
            HardwareMessage message = new HardwareMessage();

            if (args.EventType == SerialData.Chars)
            {
                byte messageType = (byte)this.mPort.ReadChar();
                switch (messageType)
                {
                    case 0x03:

                        //Read packet header
                        ushort id = (ushort)((this.mPort.ReadByte()<<8) | this.mPort.ReadByte());
                        byte dlc = (byte)this.mPort.ReadByte();
                        byte[] data = new byte[dlc];

                        //Read packet data
                        for (int i = 0; i < dlc; i++)
                        {
                            data[i] = (byte)this.mPort.ReadByte();
                        }
                        //Create packet
                        CANPacket packet = new CANPacket(id, dlc, data);

                        //Create message
                        message = new HardwareMessage(DateTime.Now, MessageType.MSG_DATA, packet);

                        //Write packet in log file
                        this.mLogFile.WriteLine(DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString() + 
                            " / ID=" + message.Data.ID + " / DLC=" + message.Data.DLC + " / DATA= " + message.Data.ToString());

                        break;
                    case 0x05:
                        message = new HardwareMessage(DateTime.Now, MessageType.MSG_INFO, "INFO");
                        break;
                    case 0x04:
                        message = new HardwareMessage(DateTime.Now, MessageType.MSG_ERROR, this.mPort.ReadLine());
                        break;
                    default:
                        message = new HardwareMessage(MessageType.MSG_UNKNOWN, messageType);
                        break;
                }
            }
            
            //Invoke event
            this.MessageReceived.Invoke(message);
        }

        //----------------------------------------------------------------------------------------

        private void ThreadProg()
        {
            Random rand = new Random();
            while (true)
            {
                int randTime = rand.Next(100, 200);

                Thread.Sleep(10);

                ushort id = (ushort)rand.Next(100, 110);
                                
                byte dlc = (byte)sizeof(double);
                byte[] data = new byte[dlc];
                double d = rand.NextDouble() * 10;
                data = BitConverter.GetBytes(d);

                HardwareMessage message = new HardwareMessage(DateTime.Now, MessageType.MSG_DATA, new CANPacket(id, dlc, data));
                this.MessageReceived.Invoke(message);
            }
        }

        //----------------------------------------------------------------------------------------

        #endregion

        #region Events

        #region Delegates

        public delegate void HardwareMessageDelegate(HardwareMessage message);

        #endregion

        public event HardwareMessageDelegate MessageReceived;

        #endregion

        #region IDisposable Member

        public void Dispose()
        {
            if (this.mIsConnected)
            {
                this.Stop();
            }

            if (this.mPort != null && this.mPort.IsOpen)
            {
                this.mPort.Close();
            }

            if (this.mLogFile != null)
            {
                this.mLogFile.Flush();
                this.mLogFile.Close();
                this.mLogFile = null;
            }
        }

        #endregion
    }
}
